/**
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

package com.aelitis.azureus.core.torrent;

import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentAnnounceURLSet;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.util.AEDiagnostics;
import org.gudy.azureus2.core3.util.AEDiagnosticsLogger;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.TorrentUtils;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.torrent.Torrent;
import org.gudy.azureus2.pluginsimpl.local.torrent.TorrentImpl;

import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreFactory;

/**
 * @author TuxPaper
 * @created Sep 27, 2006
 * 
 */
public class PlatformTorrentUtils {
    private static final long MIN_SPEED_DEFAULT = 100 * 1024;

    public static final String AELITIS_HOST_CORE = ".aelitis.com"; // needs to be lowercase
    public static final String VUZE_HOST_CORE = ".vuze.com"; // needs to be lowercase

    public static final boolean DEBUG_CACHING = System.getProperty("az3.debug.caching", "0").equals("1");

    private static final String TOR_AZ_PROP_MAP = "Content";

    private static final String TOR_AZ_PROP_HASH = "Content Hash";

    private static final String TOR_AZ_PROP_TITLE = "Title";

    private static final String TOR_AZ_PROP_DESCRIPTION = "Description";

    private static final String TOR_AZ_PROP_CONTENT_TYPE = "Content Type";

    private static final String TOR_AZ_PROP_AUTHOR = "Author";

    private static final String TOR_AZ_PROP_PUBLISHER = "Publisher";

    private static final String TOR_AZ_PROP_URL = "URL";

    private static final String TOR_AZ_PROP_THUMBNAIL = "Thumbnail";

    private static final String TOR_AZ_PROP_THUMBNAIL_URL = "Thumbnail.url";

    private static final String TOR_AZ_PROP_PROGRESSIVE = "Progressive";

    private static final String TOR_AZ_PROP_SPEED = "Speed Bps";

    private static final String TOR_AZ_PROP_MIN_SPEED = "Min Speed Bps";

    private static final String TOR_AZ_PROP_QOS_CLASS = "QOS Class";

    private static final String TOR_AZ_PROP_CONTENT_NETWORK = "Content Network";

    private static final String TOR_AZ_PROP_EXPIRESON = "Expires On";

    private static final String TOR_AZ_PROP_PRIMARY_FILE = "Primary File Index";

    private static final ArrayList<HasBeenOpenedListener> hasBeenOpenedListeners = new ArrayList<HasBeenOpenedListener>(1);

    private static final String TOR_AZ_PROP_VIDEO_WIDTH = "Video Width";

    private static final String TOR_AZ_PROP_VIDEO_HEIGHT = "Video Height";

    private static final String TOR_AZ_PROP_VIDEO_RUNNINGTIME = "Running Time";

    private static final String TOR_AZ_PROP_DURATION_MILLIS = "Duration";

    private static final String TOR_AZ_PROP_OPENED = "Opened";

    private static ArrayList<String> listPlatformHosts = new ArrayList<String>();

    static {
        for (int i = 0; i < Constants.AZUREUS_DOMAINS.length; i++) {
            listPlatformHosts.add(Constants.AZUREUS_DOMAINS[i].toLowerCase());
        }
    }

    private static final Map mapPlatformTrackerTorrents = new WeakHashMap();

    private static boolean embeddedPlayerAvail = false;

    public static Map getContentMap(TOTorrent torrent) {
        if (torrent == null) {
            return Collections.EMPTY_MAP;
        }

        Map mapAZProps = torrent.getAdditionalMapProperty(TOTorrent.AZUREUS_PROPERTIES);

        if (mapAZProps == null) {
            mapAZProps = new HashMap();
            torrent.setAdditionalMapProperty(TOTorrent.AZUREUS_PROPERTIES, mapAZProps);
        }

        Object objExistingContentMap = mapAZProps.get(TOR_AZ_PROP_MAP);

        Map mapContent;
        if (objExistingContentMap instanceof Map) {
            mapContent = (Map) objExistingContentMap;
        } else {
            mapContent = new HashMap();
            mapAZProps.put(TOR_AZ_PROP_MAP, mapContent);
        }

        return mapContent;
    }

    static Map getTempContentMap(TOTorrent torrent) {
        if (torrent == null) {
            return new HashMap();
        }

        Map mapAZProps = torrent.getAdditionalMapProperty("attributes");

        if (mapAZProps == null) {
            mapAZProps = new HashMap();
            torrent.setAdditionalMapProperty("attributes", mapAZProps);
        }

        Object objExistingContentMap = mapAZProps.get(TOR_AZ_PROP_MAP);

        Map mapContent;
        if (objExistingContentMap instanceof Map) {
            mapContent = (Map) objExistingContentMap;
        } else {
            mapContent = new HashMap();
            mapAZProps.put(TOR_AZ_PROP_MAP, mapContent);
        }

        return mapContent;
    }

    public static String getContentMapString(TOTorrent torrent, String key) {
        if (torrent == null) {
            return null;
        }

        Map mapContent = getContentMap(torrent);
        Object obj = mapContent.get(key);

        if (obj instanceof String) {
            return (String) obj;
        } else if (obj instanceof byte[]) {
            try {
                return new String((byte[]) obj, Constants.DEFAULT_ENCODING);
            } catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        return null;
    }

    private static void setContentMapString(TOTorrent torrent, String key, String value) {
        if (torrent == null) {
            return;
        }

        Map mapContent = getContentMap(torrent);
        mapContent.put(key, value);
    }

    private static long getContentMapLong(TOTorrent torrent, String key, long def) {
        if (torrent == null) {
            return def;
        }

        Map mapContent = getContentMap(torrent);
        Object obj = mapContent.get(key);

        try {
            if (obj instanceof Long) {
                return ((Long) obj).longValue();
            } else if (obj instanceof Integer) {
                return ((Integer) obj).longValue();
            } else if (obj instanceof String) {
                return Long.parseLong((String) obj);
            } else if (obj instanceof byte[]) {
                return Long.parseLong(new String((byte[]) obj));
            }
        } catch (Exception e) {
        }

        return def;
    }

    public static Map getContentMapMap(TOTorrent torrent, String key) {
        if (torrent == null) {
            return (null);
        }

        Map mapContent = getContentMap(torrent);
        Object obj = mapContent.get(key);

        if (obj instanceof Map) {
            return ((Map) obj);
        }

        return (null);
    }

    private static void setContentMapLong(TOTorrent torrent, String key, long value) {
        if (torrent == null) {
            return;
        }

        Map mapContent = getContentMap(torrent);
        mapContent.put(key, new Long(value));
    }

    public static void setContentMapMap(TOTorrent torrent, String key, Map value) {
        if (torrent == null) {
            return;
        }

        Map mapContent = getContentMap(torrent);
        mapContent.put(key, value);
    }

    public static String getContentHash(TOTorrent torrent) {
        return getContentMapString(torrent, TOR_AZ_PROP_HASH);
    }

    public static String getContentTitle(TOTorrent torrent) {
        return getContentMapString(torrent, TOR_AZ_PROP_TITLE);
    }

    public static void setContentTitle(TOTorrent torrent, String title) {
        setContentMapString(torrent, TOR_AZ_PROP_TITLE, title);
    }

    public static String getContentDescription(TOTorrent torrent) {
        return getContentMapString(torrent, TOR_AZ_PROP_DESCRIPTION);
    }

    public static void setContentDescription(TOTorrent torrent, String desc) {
        setContentMapString(torrent, TOR_AZ_PROP_DESCRIPTION, desc);
    }

    public static String getContentType(TOTorrent torrent) {
        return getContentMapString(torrent, TOR_AZ_PROP_CONTENT_TYPE);
    }

    public static void setContentType(TOTorrent torrent, String title) {
        setContentMapString(torrent, TOR_AZ_PROP_CONTENT_TYPE, title);
    }

    public static String getContentAuthor(TOTorrent torrent) {
        return getContentMapString(torrent, TOR_AZ_PROP_AUTHOR);
    }

    public static String getContentPublisher(TOTorrent torrent) {
        return getContentMapString(torrent, TOR_AZ_PROP_PUBLISHER);
    }

    public static String getContentURL(TOTorrent torrent) {
        return getContentMapString(torrent, TOR_AZ_PROP_URL);
    }

    public static long getQOSClass(TOTorrent torrent) {
        return getContentMapLong(torrent, TOR_AZ_PROP_QOS_CLASS, 0);
    }

    public static void setQOSClass(TOTorrent torrent, long cla) {
        setContentMapLong(torrent, TOR_AZ_PROP_QOS_CLASS, cla);
    }

    // public static long getContentNetworkID(TOTorrent torrent) {
    // return (getContentNetworkID(torrent, ContentNetwork.CONTENT_NETWORK_UNKNOWN));
    // }
    //
    // public static long getContentNetworkID(TOTorrent torrent, long def) {
    // long id = getContentMapLong(torrent, TOR_AZ_PROP_CONTENT_NETWORK, ContentNetwork.CONTENT_NETWORK_UNKNOWN);
    // if (id == ContentNetwork.CONTENT_NETWORK_UNKNOWN) {
    // return isContent(torrent, false) ? ContentNetwork.CONTENT_NETWORK_VUZE : def;
    // }
    // return id;
    // }
    //
    // public static void setContentNetworkID(TOTorrent torrent, long cnet) {
    // setContentMapLong(torrent, TOR_AZ_PROP_CONTENT_NETWORK, cnet);
    // }
    //
    // public static boolean isFeaturedContent(TOTorrent torrent) {
    // String content_type = getContentType(torrent);
    //
    // return (content_type != null && content_type.equalsIgnoreCase("featured"));
    // }

    private static void putOrRemove(Map map, String key, Object obj) {
        if (obj == null) {
            map.remove(key);
        } else {
            map.put(key, obj);
        }
    }

    private static void writeTorrentIfExists(TOTorrent torrent) {
        if (!AzureusCoreFactory.isCoreRunning()) {
            return;
        }
        AzureusCore core = AzureusCoreFactory.getSingleton();
        if (core == null || !core.isStarted()) {
            return;
        }

        GlobalManager gm = core.getGlobalManager();
        if (gm == null || gm.getDownloadManager(torrent) == null) {
            return;
        }

        try {
            TorrentUtils.writeToFile(torrent);
        } catch (TOTorrentException e) {
            Debug.out(e);
        }
    }

    public static byte[] getContentThumbnail(TOTorrent torrent) {
        Map mapContent = getContentMap(torrent);
        Object obj = mapContent.get(TOR_AZ_PROP_THUMBNAIL);

        if (obj instanceof byte[]) {
            return (byte[]) obj;
        }

        return null;
    }

    public static String getContentThumbnailUrl(TOTorrent torrent) {
        return getContentMapString(torrent, TOR_AZ_PROP_THUMBNAIL_URL);
    }

    public static void setContentThumbnailUrl(TOTorrent torrent, String url) {
        setContentMapString(torrent, TOR_AZ_PROP_THUMBNAIL_URL, url);
    }

    public static void setContentThumbnail(TOTorrent torrent, byte[] thumbnail) {
        Map mapContent = getContentMap(torrent);
        putOrRemove(mapContent, TOR_AZ_PROP_THUMBNAIL, thumbnail);

        writeTorrentIfExists(torrent);
    }

    public static boolean isContent(TOTorrent torrent, boolean requirePlatformTracker) {
        if (torrent == null) {
            return false;
        }
        boolean bContent = PlatformTorrentUtils.getContentHash(torrent) != null;
        if (!bContent || (bContent && !requirePlatformTracker)) {
            return bContent;
        }

        return isPlatformTracker(torrent);
    }

    public static boolean isContent(Torrent torrent, boolean requirePlatformTracker) {
        if (torrent instanceof TorrentImpl) {
            return isContent(((TorrentImpl) torrent).getTorrent(), requirePlatformTracker);
        }
        return false;
    }

    public static List<String> getPlatformHosts() {

        return listPlatformHosts;
    }

    public static void addPlatformHost(String host) {
        List<String> platformHosts = getPlatformHosts();
        host = host.toLowerCase();

        if (!platformHosts.contains(host)) {
            platformHosts.add(host);
            mapPlatformTrackerTorrents.clear();
        }
    }

    public static boolean isPlatformHost(String host) {
        Object[] domains = getPlatformHosts().toArray();

        host = host.toLowerCase();

        for (int i = 0; i < domains.length; i++) {

            String domain = (String) domains[i];

            if (domain.equals(host)) {

                return (true);
            }

            if (host.endsWith("." + domain)) {

                return (true);
            }
        }

        if (Constants.isCVSVersion()) {

            // allow local addresses for testing

            try {
                InetAddress ia = InetAddress.getByName(host);

                return (ia.isLoopbackAddress() || ia.isLinkLocalAddress() || ia.isSiteLocalAddress());

            } catch (Throwable e) {
            }
        }

        return (false);
    }

    public static boolean isPlatformTracker(TOTorrent torrent) {
        try {
            if (torrent == null) {

                return false;
            }

            Object oCache = mapPlatformTrackerTorrents.get(torrent);
            if (oCache instanceof Boolean) {
                return ((Boolean) oCache).booleanValue();
            }

            // check them all incase someone includes one of our trackers in a multi-tracker
            // torrent

            URL announceURL = torrent.getAnnounceURL();

            if (announceURL != null) {

                if (!isPlatformHost(announceURL.getHost())) {

                    mapPlatformTrackerTorrents.put(torrent, new Boolean(false));
                    return (false);
                }
            }

            TOTorrentAnnounceURLSet[] sets = torrent.getAnnounceURLGroup().getAnnounceURLSets();

            for (int i = 0; i < sets.length; i++) {

                URL[] urls = sets[i].getAnnounceURLs();

                for (int j = 0; j < urls.length; j++) {

                    if (!isPlatformHost(urls[j].getHost())) {

                        mapPlatformTrackerTorrents.put(torrent, new Boolean(false));
                        return (false);
                    }
                }
            }

            boolean b = announceURL != null;
            mapPlatformTrackerTorrents.put(torrent, new Boolean(b));
            return b;

        } catch (Throwable e) {

            Debug.printStackTrace(e);

            mapPlatformTrackerTorrents.put(torrent, new Boolean(false));
            return (false);
        }
    }

    public static boolean isPlatformTracker(Torrent torrent) {
        if (torrent instanceof TorrentImpl) {
            return isPlatformTracker(((TorrentImpl) torrent).getTorrent());
        }
        return false;
    }

    public static boolean isAdvancedViewOnly(DownloadManager dm) {
        Boolean oisUpdate = (Boolean) dm.getUserData("isAdvancedViewOnly");
        if (oisUpdate != null) {
            return oisUpdate.booleanValue();
        }

        boolean advanced_view = true;

        if (!dm.getDownloadState().getFlag(DownloadManagerState.FLAG_LOW_NOISE)) {

            TOTorrent torrent = dm.getTorrent();
            if (torrent == null) {
                advanced_view = false;
            } else {
                URL announceURL = torrent.getAnnounceURL();

                if (announceURL != null) {
                    String host = announceURL.getHost();

                    if (!(host.endsWith(AELITIS_HOST_CORE) || host.endsWith(VUZE_HOST_CORE))) {
                        advanced_view = false;
                    }
                }

                if (advanced_view) {
                    TOTorrentAnnounceURLSet[] sets = torrent.getAnnounceURLGroup().getAnnounceURLSets();

                    for (int i = 0; i < sets.length; i++) {

                        URL[] urls = sets[i].getAnnounceURLs();

                        for (int j = 0; j < urls.length; j++) {

                            String host = urls[j].getHost();

                            if (!(host.endsWith(AELITIS_HOST_CORE) || host.endsWith(VUZE_HOST_CORE))) {
                                advanced_view = false;
                                break;
                            }
                        }
                    }
                }
            }
        }

        dm.setUserData("isAdvancedViewOnly", new Boolean(advanced_view));

        return advanced_view;
    }

    public static boolean isContentProgressive(TOTorrent torrent) {
        return getContentMapLong(torrent, TOR_AZ_PROP_PROGRESSIVE, 0) == 1;
    }

    public static long getContentStreamSpeedBps(TOTorrent torrent) {
        return getContentMapLong(torrent, TOR_AZ_PROP_SPEED, 0);
    }

    public static long getContentMinimumSpeedBps(TOTorrent torrent) {
        return getContentMapLong(torrent, TOR_AZ_PROP_MIN_SPEED, MIN_SPEED_DEFAULT);
    }

    public static long getExpiresOn(TOTorrent torrent) {
        Map mapContent = getContentMap(torrent);
        Long l = (Long) mapContent.get(TOR_AZ_PROP_EXPIRESON);
        if (l == null) {
            return 0;
        }
        return l.longValue();
    }

    public static int getContentPrimaryFileIndex(TOTorrent torrent) {
        return (int) getContentMapLong(torrent, TOR_AZ_PROP_PRIMARY_FILE, -1);
    }

    private static long getContentVideoWidth(TOTorrent torrent) {
        return getContentMapLong(torrent, TOR_AZ_PROP_VIDEO_WIDTH, -1);
    }

    private static long getContentVideoHeight(TOTorrent torrent) {
        return getContentMapLong(torrent, TOR_AZ_PROP_VIDEO_HEIGHT, -1);
    }

    public static long getContentVideoRunningTime(TOTorrent torrent) {
        return getContentMapLong(torrent, TOR_AZ_PROP_VIDEO_RUNNINGTIME, -1);
    }

    public static long getContentDurationMillis(TOTorrent torrent) {
        return getContentMapLong(torrent, TOR_AZ_PROP_DURATION_MILLIS, -1);
    }

    public static void setContentDurationMillis(TOTorrent torrent, long millis) {
        setContentMapLong(torrent, TOR_AZ_PROP_DURATION_MILLIS, millis);
    }

    public static int[] getContentVideoResolution(TOTorrent torrent) {
        long width = getContentVideoWidth(torrent);
        if (width <= 0) {
            return null;
        }
        long height = getContentVideoHeight(torrent);
        if (height <= 0) {
            return null;
        }
        return new int[] { (int) width, (int) height };
    }

    public static void log(String str) {
        AEDiagnosticsLogger diag_logger = AEDiagnostics.getLogger("v3.MD");
        diag_logger.log(str);
        if (DEBUG_CACHING) {
            System.out.println(Thread.currentThread().getName() + "|" + System.currentTimeMillis() + "] " + str);
        }
    }

    /**
     * @param torrent
     * @param string
     * 
     * @since 3.0.1.5
     */
    public static void log(TOTorrent torrent, String string) {
        String hash = "";
        try {
            hash = torrent.getHashWrapper().toBase32String();
        } catch (Exception e) {
        }
        log(hash + "] " + string);
    }

    public static boolean embeddedPlayerAvail() {
        // cache true, always recheck false in case plugin installs.
        if (embeddedPlayerAvail) {
            return true;
        }

        try {
            PluginInterface pi = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaceByID("azemp", true);
            if (pi != null) {
                embeddedPlayerAvail = true;
            }
        } catch (Throwable e1) {
        }

        return embeddedPlayerAvail;
    }

    /**
     * @param torrent
     * 
     * @since 3.0.4.3
     */
    public static String getContentTitle2(DownloadManager dm) {
        if (dm == null) {
            return null;
        }
        // DM state's display name can be set by user, so show that if we have it
        String name = dm.getDownloadState().getDisplayName();
        if (name == null || name.length() == 0) {
            name = PlatformTorrentUtils.getContentTitle(dm.getTorrent());
            if (name == null) {
                name = dm.getDisplayName();
            }
        }
        return name;
    }

    public static void setHasBeenOpened(DownloadManager dm, boolean opened) {
        TOTorrent torrent = dm.getTorrent();
        if (torrent == null) {
            return;
        }
        if (opened == getHasBeenOpened(dm)) {
            return;
        }
        setContentMapLong(torrent, TOR_AZ_PROP_OPENED, opened ? 1 : 0);
        writeTorrentIfExists(torrent);
        Object[] array = hasBeenOpenedListeners.toArray();
        for (int i = 0; i < array.length; i++) {
            try {
                HasBeenOpenedListener l = (HasBeenOpenedListener) array[i];
                l.hasBeenOpenedChanged(dm, opened);
            } catch (Exception e) {
                Debug.out(e);
            }
        }
    }

    public static boolean getHasBeenOpened(DownloadManager dm) {
        TOTorrent torrent = dm.getTorrent();
        if (torrent == null) {
            return true;
        }
        boolean opened = getContentMapLong(torrent, TOR_AZ_PROP_OPENED, -1) > 0;
        if (opened || isAdvancedViewOnly(dm)) {
            return true;
        }

        return false;
    }

    public static void addHasBeenOpenedListener(HasBeenOpenedListener l) {
        hasBeenOpenedListeners.add(l);
    }
}
